﻿using System;
using System.Text;
using System.Threading;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace Server
{
    public delegate void ServerEventDelegate(object sender, ServerEventArgs e);
    public delegate void ServerInputDelegate(object sender, ServerInputArgs e);
    public delegate void ServerErrorDelegate(object sender, ServerErrorArgs e);

    public class TcpServer
    {
        private ServerSettings settings;
        private TcpListener tcpListener;
        private Thread listenThread;
        public event ServerEventDelegate AcceptConnection;
        public event ServerInputDelegate ClientInput;
        public event ServerEventDelegate ClientDisconnected;
        public event ServerErrorDelegate SocketError;

        public TcpServer()
        {
            settings = ServerSettings.Default;
            tcpListener = new TcpListener(IPAddress.Any, settings.ListenPort);
            listenThread = new Thread(new ThreadStart(Listen));
            AcceptConnection += DoNothing;
            ClientInput += DoNothing;
            ClientDisconnected += DoNothing;
            SocketError += DoNothing;
        }

        protected static void DoNothing(object sender, EventArgs e)
        {
            return;
        }

        internal ServerSettings Settings
        {
            get { return settings; }
            set { settings = value; }
        }

        public bool Running()
        {
            return (listenThread.IsAlive);
        }

        public void Stop()
        {
            if (listenThread.IsAlive)
                listenThread.Abort();
            else
                throw new InvalidOperationException("Server not started");
        }

        public void Start()
        {
            if (!listenThread.IsAlive)
                listenThread.Start();
            else
                throw new InvalidOperationException("Server already started");
        }

        private void Listen()
        {
            tcpListener.Start();
            while (true)
            {
                TcpClient cli = tcpListener.AcceptTcpClient();

                Thread t = new Thread(new ParameterizedThreadStart(AcceptClient));
                AcceptConnection.Invoke(this, new ServerEventArgs(cli));

                t.Start(cli);
            }
        }

        protected void SendLine(Stream stream, string str)
        {
            SendString(stream, str + "\r\n");
        }

        protected void SendLine(TcpClient client, string str)
        {
            SendLine(client.GetStream(), str);
        }

        protected void SendString(TcpClient client, string str)
        {
            SendString(client.GetStream(),str);
        }

        protected void SendString(Stream netStream, string str)
        {
            char[] CharArray = str.ToCharArray();
            byte[] ByteArray = new byte[CharArray.Length];
            for (int i = 0; i < CharArray.Length; i++)
                ByteArray[i] = Convert.ToByte(CharArray[i]);
            netStream.Write(ByteArray, 0, ByteArray.Length);
        }
            
        private void AcceptClient(object o)
        {
            bool abort = false, error = false;
            Exception err = null;
            String line;
            TcpClient client = (TcpClient)o;
            NetworkStream str = client.GetStream();
            byte[] message = new byte[settings.MessageArraySize];
            int bytesRead;

            /*
             * This if-check was added because, when I use Windows Telnet, there IS no
             * initial gunk in the stream, hence this line causes problems
             * I can't get Putty to connect properly (it crashes when I try), so I'm
             * not sure if this still works to flush the gunk that Putty throws in there
             */
            if (client.Available > 0) // UNTESTED ON PUTTY
                str.Read(message, 0, settings.MessageArraySize);  //Clear the initial gunk in stream
            SendLine(str, "You are now connected to Group 10's server!");

            while (true)
            {
                line = "";
                bytesRead = 0;
                do
                {
                    try
                    {
                        bytesRead = str.Read(message, 0, settings.MessageArraySize);
                    }
                    catch (IOException ex)          //Socket closed
                    {
                        error = true;
                        err = ex;
                        break;
                    }
                    catch (ObjectDisposedException ex) //NetworkStream closed or failure reading from network
                    {
                        error = true;
                        err = ex;
                        break;
                    }

                    if (bytesRead == 0)             // Client has disconnected
                    {
                        abort = true;
                        break;
                    }

                    line += Encoding.ASCII.GetString(message,0,bytesRead);
                } while (!line.EndsWith("\n"));

                if (error)
                {
                    SocketError.Invoke(this, new ServerErrorArgs(client,err));
                    return;
                }

                if (abort) break;

                ClientInput.Invoke(this, new ServerInputArgs(client, line.Trim()));
            }

            ClientDisconnected.Invoke(this, new ServerEventArgs(client));

            client.Close();
        }
    }
}
